/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.situations;

import static robocup.component.DAInamiteConstants.DRIBBLE_TURN_ANGLE;

import java.util.LinkedList;

import robocup.component.DAInamiteConstants;
import robocup.component.Prophet;
import robocup.component.SConf;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.STAMINA_LEVEL;
import robocup.component.actionfactories.BasicActionFactory;
import robocup.component.actions.Action;
import robocup.component.actions.DashAction;
import robocup.component.actions.KickAction;
import robocup.component.geometry.Circle;
import robocup.component.geometry.Vektor;
import robocup.component.math.RCMath;
import robocup.component.worldmodel.PlayersModel.SORTED;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Line;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;
import robocup.component.worldobjects.Player.ROLE;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * The dribble situation.<br>
 * 
 * This situation matches worlds where the player controls the ball. It 
 * tries to find out the best dribbling action and the benefit for that action.
 * <br>
 * Even without opponents (fast) dribbling can be a complicated procedure. With 
 * opponent(s) very near we nearly always prefer passing as this situation is
 * still unfinished and works far from perfect :-)!
 * <br><br>
 * 
 * TODO
 * 
 * - this is only patchwork, seems not buggy but should be improved
 */
public class DribbleSituation extends Situation {

	private static String DEBUG_SLOWDRIBBLING = "slow";

	private static String DEBUG_FASTDRIBBLING = "fast";

	private static String DEBUG_TURNING = "turning";

	private static String DEBUG_WAITWITHBALL = "waitwithball";

	private static String DEBUG_SAVINGSECURE = "savingsec";

	private static String DEBUG_SAVINGUNSECURE = "savingunsec";

	private static String DEBUG_OUTPLAYGOALIE = "outplaygoalie";

	// the max security (see SituationEvaluator)
	public static int maxSecurity = 4;

	// Dribble Types
	public enum TYPE {
    //not implemnted yet
		BACKWARD_DRIBBLING, 
    
    // dribbling with no opponents in dribble direction
    FAST_DRIBBLING, 
    
    // slow and secure dribbling
    SLOW_DRIBBLING, 
    
    //change dribble dir
    TURNING, 
    
    //wait with the ball at the current position
    WAIT_WITH_BALL,

		// Opponent can get the ball in 1, but we can save it
		SAVING_SECURE,

		// Opponent can get the ball in 1 and we can't save it
		SAVING_UNSECURE
	}

	/**
	 * a tackling player = player + tackle actions + tackleprob(action) +
	 * resulting_players(action)
   * 
   * used for storage.
	 */
	private class TacklingPlayer {

		// the player
		private Player player;

		// all resulting players when trying to tackle
		LinkedList<Player> p_tackling = new LinkedList<Player>();

		// all actions that can lead to tackling (can be 1 for Vi and 2 for BS)
		LinkedList<LinkedList<Action>> actions = new LinkedList<LinkedList<Action>>();

		// the tackle probs
		LinkedList<Double> tackleProbs = new LinkedList<Double>();

		/**
		 * Constructor.
		 * 
		 * @param p -
		 *            the player
		 */
		public TacklingPlayer(Player p) {

			this.player = p;
		}

		/**
		 * adds the data.
		 * 
		 * @param p_before_tackling -
		 *            the player after acting but before tackling
		 * @param actions_before_tackling -
		 *            the actions done by the player to reach the tackle
		 *            possibility
		 * @param tackleProb -
		 *            the resulting tackle probability
		 */
		public void addTacklePossibility(Player p_before_tackling,
				LinkedList<Action> actions_before_tackling, double tackleProb) {

			p_tackling.add(p_before_tackling);
			actions.add(actions_before_tackling);
			tackleProbs.add(new Double(tackleProb));
		}

		/**
		 * @return Returns the actions.
		 */
		public LinkedList<LinkedList<Action>> getActions() {

			return this.actions;
		}

		/**
		 * @return Returns the p_tackling.
		 */
		public LinkedList<Player> getP_tackling() {

			return this.p_tackling;
		}

		/**
		 * @return Returns the player.
		 */
		public Player getPlayer() {

			return this.player;
		}

		/**
		 * @return Returns the tackleProbs.
		 */
		public LinkedList<Double> getTackleProbs() {

			return this.tackleProbs;
		}
	}

	// dummy objects
	private Player dummyPlayer = Player.getDummyInstance();

	private Vektor dummyVektor = Vektor.getDummyInstance();

	private Vektor dummyVektor2 = Vektor.getDummyInstance();

	private DashAction dummyDashAction = new DashAction(0);

  private DashAction dash100 = new DashAction(100);

  private DashAction dash50 = new DashAction(50);

  // the next self (without acting)
	private Player nextSelf = Player.getDummyInstance();

	// the next ball (without kicking)
	private Ball nextBall = Ball.getDummyInstance();

	// the opps ordered by distance
	private Player[] opponentsByDistance;

	// buffer for different opponents
	private LinkedList<Player> oppsInBodyDir = new LinkedList<Player>();

	private LinkedList<Player> oppsInGoalDir = new LinkedList<Player>();

	private LinkedList<Player> oppsInBackDir = new LinkedList<Player>();

	private LinkedList<Player> oppsNear = new LinkedList<Player>();

	private LinkedList<Player> oppsRelevant = new LinkedList<Player>();

	private LinkedList<Player> oppsRight = new LinkedList<Player>();

	private LinkedList<Player> oppsLeft = new LinkedList<Player>();

	// opponents that can tackle in 1
	private LinkedList<TacklingPlayer> oppsTackling = new LinkedList<TacklingPlayer>();

  //last dribbling type
	private TYPE lastType = TYPE.WAIT_WITH_BALL;

  //benefit for passing
	private double passBenefit = 0.0;

  //the benefit for scoring
  private double scoreBenefit;

  //first guess dribbling benefit
	private double defaultBenefit = 0.0;

	private boolean DEBUG = false;

  //true if there is a opponent that can tackle the ball in the next cycle
	private boolean opponentCanTackleIn1;

	// the best benefit of the other states by now (Max(KickNRun, Passing))
	private double bestBenefit;

	// the best point to save the ball when an opponent is near enough to tackle
	private Vektor mostSecurePoint = Vektor.getDummyInstance();

	// the point to dribble to
	private Vektor pointToGo = Vektor.getDummyInstance();

	/**
	 * Constructor - a situation, which predicts the effectiveness of different
	 * dribble alternatives.
	 * 
	 * @param world -
	 *            the current world-model
	 * @param prophet -
	 *            the prediction device and parent object of this
	 * @param movements -
	 *            a generic movement model
	 */
	public DribbleSituation(WorldModel world, Prophet prophet,
			Movements movements) {

		super(world, prophet, movements);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see robocup.component.situations.Situation#update()
	 */
	public void update() {

		super.update();

		this.reset();

		if (this.shouldBeCalculated()) {
			this.calculate();

			this.checkForTackling();
		}
	}

	/**
	 * the situation is not calculated if the passing or scoring benefit is to 
   * high.
	 * 
	 * @return
	 */
	private boolean shouldBeCalculated() {

		boolean b = this.defaultBenefit > this.bestBenefit * 0.9;
		if (DEBUG && !b) {
			System.out
					.println(this.getWorld().getCycleAndPlayerString()
							+ " DribbeSituation. No calculation as benefit is already to high.  Best:"
							+ this.bestBenefit + " My:" + this.defaultBenefit);
		}
		return b;

	}

	/**
	 * reset all values
	 */
	private void reset() {

		this.bestAction = null;
		this.benefit = 0.0;
		this.defaultBenefit = 0.0;

		this.opponentsByDistance = this.getWorld().getPlayersSortedBy(
				SORTED.EXPECTED_OPPONENTS_BY_DISTANCE_ASC,
				this.getWorld().getSelfRef().getPosition());

		this.oppsInBackDir.clear();
		this.oppsInBodyDir.clear();
		this.oppsInGoalDir.clear();
		this.oppsRelevant.clear();
		this.oppsNear.clear();
		this.oppsLeft.clear();
		this.oppsRight.clear();
		this.oppsTackling.clear();

		this.mostSecurePoint.setEmpty();

		this.nextBall.copy(this.getWorld().getBallRef());
		this.nextBall.extrapolate(1, SConf.getInstance().BALL_DECAY);

		this.nextSelf.copy(this.getWorld().getSelfRef());
		this.nextSelf.predictThisAfterAction(null);

		this.setBestDribbleDir();

		// use passbenefit as a limit
		this.passBenefit = this.getProphet().getPassBenefit();
		this.scoreBenefit = this.getProphet().getScoreBenefit();

		this.bestBenefit = this.passBenefit;
		this.bestBenefit = Math.max(this.benefit, this.scoreBenefit);
	}

	/**
	 * main method which decides which sub methods should be invoked
	 * 
	 */
	private void calculate() {

		if (DEBUG)
			System.out.println(this.getWorld().getCycleAndPlayerString()
					+ " Calculate Dribbling (Best: " + this.bestBenefit
					+ " Passing:" + this.passBenefit + ")");

    //filter unimportant opponents
		this.loadOpponents();

		// if we can dash opponents might assume we dash and the ball remains as
		// it is so we assume that they try to tackle the next ball
		if (this.nextballCanBeReached()) {
			this.opponentCanTackleIn1 = this.opponentCanTackleIn1(
					this.oppsNear, this.nextBall.getPosition());
		}
    
		// if we have to kick the ball in order to stop it we assume that
		// opponents will turn against our position instead against the next ball 
    // which they don't know about
		else {
			this.opponentCanTackleIn1 = this.opponentCanTackleIn1(
					this.oppsNear, this.nextSelf.getPosition());
		}

		// TODO special dribbling against goalie
		if ((oppsInBodyDir.size() > 0 && oppsInBodyDir.get(0).isGoalie())
				|| (oppsInGoalDir.size() > 0 && oppsInGoalDir.get(0).isGoalie())
				|| (oppsRelevant.size() > 0 && oppsRelevant.get(0).isGoalie())) {
			this.subStateString = DEBUG_OUTPLAYGOALIE;
			this.outplayGoalie();
		}
		
    //turn when in wrong direction
		else if (this.gotWrongBodyDir()
				&& (oppsNear.size() == 0 || (oppsInBodyDir.size() > 0 && oppsInBodyDir
						.get(0).getDistance(nextBall) < 4)
						&& oppsNear.get(0).getDistance(nextSelf.getPosition()) > 3)) {
			this.subStateString = DEBUG_TURNING;
			this.doTurning();
		}

		else {
			double distanceToGoal = this.nextSelf.getDistance(SConf
					.getInstance().GOAL_POS_OTHER);
			
      //wait if no stamina left and no teammate joined the attack
			if ((distanceToGoal > 30 && nextSelf.getStamina() < DAInamiteConstants.STAMINA_THR_MID_LOW)
					|| nextSelf.getStamina() < DAInamiteConstants.STAMINA_THR_LOW + 100) {
				this.subStateString = DEBUG_WAITWITHBALL;
				this.waitWithBall();
			} 
      //dribble fast if no opponent in the way
      else if (oppsInBodyDir.size() == 0
					|| oppsInBodyDir.get(0).getDistance(nextSelf) > 5) {
				this.subStateString = DEBUG_FASTDRIBBLING;
				this.doFastDribbling();
			} 
      //dribble slow otherwise
      else {
				this.subStateString = DEBUG_SLOWDRIBBLING;
				this.doSlowDribbling();

			}
		}
		// check wether the action we intend to do leaves the opponent a tackle
		// possiblity
		this.checkForTackling();

    //check wether the bestAction would kick the ball outside
		this.checkBallOutside();

    //dashing is prefered to turning as it gets us nearer to the dribbling
    //position
		if (this.bestAction != null
				&& this.bestAction.getType() == Action.TYPE.DASH) {
			this.benefit *= 1.1;
			if (DEBUG)
				System.out.println("No Dash -> benefit set to " + this.benefit);
		}
	}

	/**
	 * finds out the best dribble dir by checking the benefit of each dir.
	 * 
	 */
	private void setBestDribbleDir() {

		double angleAbs;
		double benefit;
		int security = 5;

		// if we are already dribbling in the best dir, turning is punished
		boolean isAlreadyBestDir = true;

    //the bestpos to dribble to depends on the players role
		Vektor bestPos;

		if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
				.getRoleByRoleNumber())) {
			bestPos = SConf.getInstance().GOAL_POST_LEFT_OTHER;
		} else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
				.getRoleByRoleNumber())) {
			bestPos = SConf.getInstance().GOAL_POST_RIGHT_OTHER;
		} else {
			bestPos = SConf.getInstance().GOAL_POS_OTHER;
		}

		// deciding if we are dribbling in a good direction
    
    //logic for penalty area
		if (this.getWorld().inOpponentsPenalty(this.nextSelf)
				|| this.nextSelf.getPosition().y > 40) {
			if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
					.getRoleByRoleNumber())) {
				dummyVektor.x = 0;
				dummyVektor.y = 38;
				isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
						this.nextSelf.getPosition(),
						SConf.getInstance().GOAL_POST_LEFT_OTHER, dummyVektor);
			} else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
					.getRoleByRoleNumber())) {
				dummyVektor.x = 0;
				dummyVektor.y = 38;
				isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
						this.nextSelf.getPosition(), dummyVektor, SConf
								.getInstance().GOAL_POST_RIGHT_OTHER);
			} else {
				isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
						this.nextSelf.getPosition(),
						SConf.getInstance().GOAL_POST_LEFT_OTHER, SConf
								.getInstance().GOAL_POST_RIGHT_OTHER);
			}
		} 
    
    //logic for outside penalty area
    else {
			if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
					.getRoleByRoleNumber())) {
				dummyVektor.x = -SConf.getInstance().half_width + 3;
				dummyVektor.y = SConf.getInstance().half_length;
				isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
						this.nextSelf.getPosition(), dummyVektor, SConf
								.getInstance().GOAL_POS_OTHER);

				dummyVektor.pointAtPolar(8, this.nextSelf.getBodyDir());
				dummyVektor.addToThis(nextSelf.getPosition());

				if (dummyVektor.x < -SConf.getInstance().half_width + 2
						&& this.nextSelf.getBodyDir() > -30)
					isAlreadyBestDir = true;
			} else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
					.getRoleByRoleNumber())) {
				dummyVektor.x = SConf.getInstance().half_width - 3;
				dummyVektor.y = SConf.getInstance().half_length;
				isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
						this.nextSelf.getPosition(),
						SConf.getInstance().GOAL_POS_OTHER, dummyVektor);

				dummyVektor.pointAtPolar(8, this.nextSelf.getBodyDir());
				dummyVektor.addToThis(nextSelf.getPosition());

				if (dummyVektor.x > SConf.getInstance().half_width - 2
						&& this.nextSelf.getBodyDir() < 30)
					isAlreadyBestDir = true;
			} else {
				isAlreadyBestDir = RCMath.isBetween(this.nextSelf.getBodyDir(),
						this.nextSelf.getPosition(),
						SConf.getInstance().GOAL_POST_LEFT_OTHER, SConf
								.getInstance().GOAL_POST_RIGHT_OTHER);
			}
		}

		if (DEBUG)
			System.out.print("Trying to find best dir: alreadyBest = "
					+ isAlreadyBestDir + " ");

    //how often would we need for turning and how much time will the opponent
    //give us, turning is most often punished as we prefer dashing
		double turnToGoalFact = 1.0;
		if (!isAlreadyBestDir) {
			int timeForTurning = this.oppsInGoalDir.size() == 0 ? 10
					: (int) this.oppsInGoalDir.get(0)
							.getDistance(this.nextSelf) - 1;
			if (this.nextballCanBeReached()) {
				timeForTurning--;
			} else {
				timeForTurning -= 2;
			}

			if (timeForTurning > 3) {
				turnToGoalFact = Math.pow(0.8, timeForTurning - 3);
			}
		}

		double angleToGoalAbs = this.nextSelf.getAngleTo(bestPos);

		// try to find the benefit of dribbling in the driection of each
		// angle using the SituationEvaluator
		for (double angle = -180; angle <= 180; angle += DRIBBLE_TURN_ANGLE) {
			angleAbs = Vektor.normalize(angle
					+ this.getWorld().getSelfRef().getBodyDir());
			this.dummyVektor.pointAtPolar(2, angleAbs);
			this.dummyVektor.addToThis(this.nextSelf.getPosition());

			this.dummyVektor2.pointAtPolar(5, angleAbs);
			this.dummyVektor2.addToThis(this.nextSelf.getPosition());

			if (this.getWorld().inField(dummyVektor2)) {

				Player opp = this.getWorld().getClosestOpponentTo(
						dummyVektor);

				security = opp == null ? maxSecurity : Math
						.min((int) dummyVektor.getDistance(opp
								.getPosition()) - 1, maxSecurity);

				benefit = SituationEvaluator.getValue(
						SituationEvaluator.TYPE.DRIBBLING, this.getWorld()
								.getBallRef().getPosition(), dummyVektor,
						security, 1, this.getWorld().getSelfRef(), this
								.getWorld());

				// punishing turning if dribbling in a good dir
				if (angle != 0 && isAlreadyBestDir)
					benefit *= 0.8;

				if (Math.abs(Vektor.normalize(angleToGoalAbs - angleAbs)) > 30
						&& !isAlreadyBestDir) {
					benefit *= turnToGoalFact;
				}

				if (benefit > this.defaultBenefit) {
					this.defaultBenefit = benefit;
					this.pointToGo.copy(this.dummyVektor);
					if (DEBUG)
						System.out.print("\t" + this.pointToGo + ":"
								+ this.defaultBenefit + ":"
								+ turnToGoalFact);
				}

			}
		}

		if (DEBUG)
			System.out.println();
	}

	/**
	 * sets the relevant opponents and filters all others.
	 * 
	 */
	private void loadOpponents() {

		double distance;
		double angleToBody;
		double angleToGoal = this.getWorld().getSelfRef().getAngleRel(
				SConf.getInstance().GOAL_POS_OTHER, RELATIVE.TO_BODY);
		double maxAngle;

		for (Player opp : this.opponentsByDistance) {
			distance = this.getWorld().getSelfRef().getDistance(opp);
			if (distance > 15) {
				break;
			} else {
				this.oppsRelevant.add(opp);

				angleToBody = this.getWorld().getSelfRef().getAngleRel(opp,
						RELATIVE.TO_BODY);
				maxAngle = this.getAngleForDistance(distance);

				if (maxAngle > Math.abs(angleToBody)) {
					this.oppsInBodyDir.add(opp);
				}

				if (maxAngle > Math.abs(Vektor.normalize(180.0 - angleToBody))) {
					this.oppsInBackDir.add(opp);
				}

				if (maxAngle > Math.abs(Vektor.normalize(angleToGoal
						- angleToBody))) {
					this.oppsInGoalDir.add(opp);
				}

				if (angleToBody < 90 + maxAngle && angleToBody > 90 - maxAngle) {
					this.oppsRight.add(opp);
				}

				if (angleToBody > -90 - maxAngle
						&& angleToBody < -90 + maxAngle) {
					this.oppsLeft.add(opp);
				}

				if (distance < 4.5) {
					this.oppsNear.add(opp);
				}

			}
		}

	}

	/**
	 * help method for telling which opponents to consider. 
   * Returns the angle to consider the opponent as "in the way" which may differ
   * depending on the opponent's distance.
	 * 
	 * @param distance -
	 *            the opponents distance
	 * @return the angle to consider the opponent as "in the way"
	 */
	private double getAngleForDistance(double distance) {

		if (distance > 6)
			return 45.0;

		if (distance > 3)
			return 60.0;

		return 75.0;
	}

	/**
	 * checks wether an opponent can tackle a point.
   * Opponent which can tackle this point are added to the tackler list.
	 * 
	 * @param opponents -
	 *            the opponents to consider
	 * @return true if an opponent can tackle in 1 cycle when the ball remains
	 *         as it is (2 cycles if based on bs)
	 */
	private boolean opponentCanTackleIn1(LinkedList<Player> opponents,
			Vektor point) {

		Action a;
		Action b;

		double tackleProb = 0.0;

		// the world is vi based. opponent has 1 action before tackling
		// check turning and dashing
		if (this.getWorld().isViBased()) {
			double tackleProbTurning = 0.0;
			double tackleProbDashing = 0.0;

			for (Player opp : opponents) {
				TacklingPlayer tackler = new TacklingPlayer(opp);
				LinkedList<Action> actions = new LinkedList<Action>();

				this.dummyPlayer.copy(opp);
				a = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(a);

				tackleProbTurning = this.dummyPlayer
						.tackleSuccessProbability(point);
				tackleProb = Math.max(tackleProb, tackleProbTurning);

				if (tackleProb > 0) {
					actions.add(a);
					tackler.addTacklePossibility(this.dummyPlayer.cloned(),
							actions, tackleProb);
					if (DEBUG)
						System.out.println("Opponent can tackle in 1 Prob:"
								+ tackleProbTurning + " maxProb:" + tackleProb
								+ " Action:" + a);
				}

				this.dummyPlayer.copy(opp);
				a = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(a);

				tackleProbDashing = this.dummyPlayer
						.tackleSuccessProbability(point);
				tackleProb = Math.max(tackleProb, tackleProbDashing);

				if (tackleProb > 0) {
					actions.add(a);
					tackler.addTacklePossibility(this.dummyPlayer.cloned(),
							actions, tackleProb);
					if (DEBUG)
						System.out.println("Opponent can tackle in 1 Prob:"
								+ tackleProbDashing + " maxProb:" + tackleProb
								+ " Action:" + a);
				}

				if (tackler.getActions().size() > 0)
					this.oppsTackling.add(tackler);
			}
		}
		// the world is bs based. opponent has 2 actions before tackling
		// check turning+dashing and 2x turning and 2x dashing
		else {
			double tackleProbTurning2x = 0.0;
			double tackleProbDashing2x = 0.0;
			double tackleProbDashingTurning = 0.0;
			double tackleProbTurningDashing = 0.0;

			for (Player opp : opponents) {
				TacklingPlayer tackler = new TacklingPlayer(opp);
				LinkedList<Action> actions = new LinkedList<Action>();

				// 2x truning
				this.dummyPlayer.copy(opp);
				a = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(a);
				b = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(b);

				tackleProbTurning2x = this.dummyPlayer
						.tackleSuccessProbability(point);
				tackleProb = Math.max(tackleProb, tackleProbTurning2x);

				if (tackleProb > 0) {
					actions.add(a);
					actions.add(b);
					tackler.addTacklePossibility(this.dummyPlayer.cloned(),
							actions, tackleProb);

					if (DEBUG)
						System.out.println("Opponent " + opp.getNumber()
								+ " can tackle Prob:" + tackleProbTurning2x
								+ " maxProb:" + tackleProb + " Action1:" + a
								+ " Action2:" + b);
				}

				// 2x dashing
				this.dummyPlayer.copy(opp);
				a = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(a);
				b = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(b);

				tackleProbDashing2x = this.dummyPlayer
						.tackleSuccessProbability(point);
				tackleProb = Math.max(tackleProb, tackleProbDashing2x);

				if (tackleProb > 0) {
					actions.add(a);
					actions.add(b);
					tackler.addTacklePossibility(this.dummyPlayer.cloned(),
							actions, tackleProb);

					if (DEBUG)
						System.out.println("Opponent " + opp.getNumber()
								+ " can tackle Prob:" + tackleProbDashing2x
								+ " maxProb:" + tackleProb + " Action1:" + a
								+ " Action2:" + b);
				}

				// dashing + turning
				this.dummyPlayer.copy(opp);
				a = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(a);
				b = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(b);

				tackleProbDashingTurning = this.dummyPlayer
						.tackleSuccessProbability(point);
				tackleProb = Math.max(tackleProb, tackleProbDashingTurning);

				if (tackleProb > 0) {
					actions.add(a);
					actions.add(b);
					tackler.addTacklePossibility(this.dummyPlayer.cloned(),
							actions, tackleProb);

					if (DEBUG)
						System.out.println("Opponent " + opp.getNumber()
								+ " can tackle Prob:"
								+ tackleProbDashingTurning + " maxProb:"
								+ tackleProb + " Action1:" + a + " Action2:"
								+ b);
				}

				// turning + dashing
				this.dummyPlayer.copy(opp);
				a = BasicActionFactory.turnBodyToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(a);
				b = BasicActionFactory.dashToPoint(this.dummyPlayer, point);
				this.dummyPlayer.predictThisAfterAction(b);

				tackleProbTurningDashing = this.dummyPlayer
						.tackleSuccessProbability(point);
				tackleProb = Math.max(tackleProb, tackleProbTurningDashing);

				if (tackleProb > 0) {
					actions.add(a);
					actions.add(b);
					tackler.addTacklePossibility(this.dummyPlayer.cloned(),
							actions, tackleProb);

					if (DEBUG)
						System.out.println("Opponent " + opp.getNumber()
								+ " can tackle Prob:"
								+ tackleProbTurningDashing + " maxProb:"
								+ tackleProb + " Action1:" + a + " Action2:"
								+ b);
				}

				if (tackler.getActions().size() > 0)
					this.oppsTackling.add(tackler);
			}
		}

		return tackleProb > 0.0;
	}

	/**
	 * checks if the ball resulting from our bestAction may result in the
   * tackling area of an opponent. If so it tries to move the ball outside
   * the opponents tackle area.
	 */
	private void checkForTackling() {

		if (this.benefit == 0.0 || this.lastType == TYPE.SAVING_UNSECURE
				|| this.lastType == TYPE.SAVING_SECURE)
			return;

		LinkedList<Vektor> points = new LinkedList<Vektor>();

		Ball ball1 = this.getWorld().getBallRef().predictBallAfterAction(
				this.getWorld().getSelfRef(), this.bestAction);
		points.add(ball1.getPosition());

		LinkedList<Vektor> goodPoints = this
				.checkPositionsAgainstTackling(points);

		if (goodPoints.size() == 1) {
			if (DEBUG)
				System.out.println("Opponent can't tackle");
			return;
		}

		if (DEBUG)
			System.out.print("Opponent can tackle -> saving ball..");

		this.saveBallFromOpponent();
	}

	/**
	 * checks if the ball results outside and perfoms the action to keep it inside
	 * the field
	 */
	private void checkBallOutside() {

		Ball resultingBall = this.getWorld().getBallRef()
				.predictBallAfterAction(this.getWorld().getSelfRef(),
						this.bestAction);
		if (!this.getWorld().inField(resultingBall.getPosition())) {
			if (DEBUG)
				System.out.print("Next Ball outside!! ");
			
      this.benefit *= 0.5;

			// opponent might get it in 1
			if (this.lastType == TYPE.SAVING_SECURE
					|| this.lastType == TYPE.SAVING_UNSECURE) {
				// near own goal
				if (this.nextSelf.getDistance(SConf.getInstance().GOAL_POS_OWN) < 20) {
					// let ball going out
					if (resultingBall.getPosition().x > 8) {
						if (DEBUG)
							System.out.print(" -> let it go (near goal).");
						return;
					}
					// get ball away from were it is
					else {
						this.benefit *= 0.2;

						// try to get the ball in front of the player so we will
						// have max kick and tackle power in the next cycle
						this.dummyVektor.pointAtPolar(0.4, this.nextSelf
								.getBodyDir());
						this.dummyVektor.addToThis(this.nextSelf.getPosition());
						if (this.getWorld().inField(this.dummyVektor)) {
							this.bestAction = BasicActionFactory.kickInTime(
									this.getWorld().getSelfRef(), this
											.getWorld().getBallRef(),
									this.dummyVektor, 1, 0);
							if (DEBUG)
								System.out
										.print(" -> kicking in front (near goal)."
												+ bestAction);
							if (this.bestAction == null) {
								dummyVektor.x = 20 * Math.signum(nextSelf
										.getPosition().x);
								dummyVektor.y = -45;
								this.bestAction = BasicActionFactory
										.kickFastest(this.getWorld()
												.getSelfRef(), this.getWorld()
												.getBallRef(), dummyVektor);
								if (DEBUG)
									System.out
											.print(" -> kicking to side (near goal)"
													+ bestAction + dummyVektor);
							}
						}
						// if the player is outside we kick the ball away to the
						// side
						else {
							dummyVektor.x = 20 * Math.signum(nextSelf
									.getPosition().x);
							dummyVektor.y = -45;
							this.bestAction = BasicActionFactory.kickFastest(
									this.getWorld().getSelfRef(), this
											.getWorld().getBallRef(),
									dummyVektor);
							if (DEBUG)
								System.out
										.print(" -> kicking to side (near goal)."
												+ bestAction);
						}
					}
				}
				// ball not near own goal
				else {
					this.dummyVektor.pointAtPolar(0.4, this.nextSelf
							.getBodyDir());
					this.dummyVektor.addToThis(this.nextSelf.getPosition());
					if (this.getWorld().inField(this.dummyVektor)) {
						this.bestAction = BasicActionFactory.kickInTime(this
								.getWorld().getSelfRef(), this.getWorld()
								.getBallRef(), this.dummyVektor, 1, 0);
						if (DEBUG)
							System.out.println("-> kicking in front"
									+ this.bestAction);
						if (this.bestAction == null) {
							this.bestAction = this.stopBall();
							if (DEBUG)
								System.out.println("-> stopping ball"
										+ this.bestAction);
						}
					} else {
						this.bestAction = this.stopBall();
						if (DEBUG)
							System.out.println("-> stopping ball"
									+ this.bestAction);
					}
				}
			}
			// we have time
			else {
				this.dummyVektor.pointAtPolar(0.4, this.nextSelf.getBodyDir());
				this.dummyVektor.addToThis(this.nextSelf.getPosition());
				if (this.getWorld().inField(this.dummyVektor)) {
					this.bestAction = BasicActionFactory.kickInTime(this
							.getWorld().getSelfRef(), this.getWorld()
							.getBallRef(), this.dummyVektor, 1, 0);
					if (DEBUG)
						System.out.println("-> kicking in front"
								+ this.bestAction);
					if (this.bestAction == null) {
						this.bestAction = this.stopBall();
						if (DEBUG)
							System.out.println("-> stopping ball"
									+ this.bestAction);
					}
				} else {
					this.bestAction = this.stopBall();
					if (DEBUG)
						System.out
								.println("-> stopping ball" + this.bestAction);
				}
			}
		}
	}

	/**
	 * @return true if we can get the next ball without a kick.
	 */
	private boolean nextballCanBeReached() {

		Action dash = BasicActionFactory.dashToPoint(this.getWorld()
				.getSelfRef(), this.nextBall.getPosition());

		return this.getWorld().getSelfRef().predictPlayerAfterAction(dash)
				.canKick(this.nextBall);
	}

	/**
	 * turns to the best dir. stops the ball first if needed.
	 */
	private void doTurning() {

		if (DEBUG)
			System.out.print("Turning ");
		boolean nextBallIsKickableAndSlow = nextBall.getSpeed().getLength() < 0.4
				&& nextSelf.getDistance(nextBall) < 0.8;

		if (nextBallIsKickableAndSlow && !this.opponentCanTackleIn1) {
			this.bestAction = BasicActionFactory.turnBodyToPoint(this
					.getWorld().getSelfRef(), this.pointToGo);
			this.setBenefit(TYPE.TURNING, this.getSecurity(TYPE.TURNING));
			if (DEBUG)
				System.out.println(" to " + this.pointToGo);
		} else {
			this.bestAction = this.stopBall();
			this.setBenefit(TYPE.TURNING, this.getSecurity(TYPE.TURNING));
			if (DEBUG)
				System.out.println(" stopping ball first");
		}
	}

	/**
	 * tries to find a secure point assuming that the opponents will try to
	 * tackle the ball in 1
	 */
	private void saveBallFromOpponent() {

		LinkedList<Player> playersThatCanKickIn1 = new LinkedList<Player>();

		for (Player p : this.getProphet().getFastestPlayerToBall()) {
			if (!p.isFriend()) {
				if (this.getProphet().getInterceptionTime(p) == 1) {
					playersThatCanKickIn1.add(p);
				} else {
					break;
				}
			}
		}

		if (DEBUG) {
			System.out.println("Saving Ball from Opponents: ");
			for (Player opp : playersThatCanKickIn1) {
				System.out.println("KickingIn1:" + opp
						+ this.getProphet().getNextInterceptAction(opp));
			}
			for (TacklingPlayer opp : this.oppsTackling) {
				System.out.print("TacklingIn1:" + opp.getPlayer());
				for (LinkedList<Action> actions : opp.getActions()) {
					for (Action a : actions) {
						System.out.print(a);
					}
					System.out.print(" - ");
				}
				System.out.print(" || ");
			}

		}

		// the benefit decreases when opponent might get thte ball
		this.defaultBenefit /= (1 + playersThatCanKickIn1.size() + this.oppsTackling
				.size());
		if (this.bestBenefit > this.defaultBenefit) {
			this.benefit = -this.bestBenefit;
			this.bestAction = null;

			if (DEBUG) {
				System.out
						.println("No Saving as passBenefit to high! -> Best: "
								+ this.bestBenefit + " Dribble:"
								+ this.defaultBenefit + "");
			}
			return;
		}

		// now we generate a number of test points
		// each tackling approach is answered by moving the ball away from the
		// opponent
		LinkedList<Vektor> testPoints = new LinkedList<Vektor>();

		for (TacklingPlayer opp : this.oppsTackling) {
			for (int move = 0; move < opp.getActions().size(); move++) {
				Action a = opp.getActions().get(move).getLast();
				switch (a.getType()) {
				case TURN:
					Vektor pos = opp.getP_tackling().get(move).getVektorTo(
							this.nextSelf);
					pos.setLength(0.9);
					testPoints.add(pos);
					if (DEBUG)
						System.out.print("Added save point " + pos);
					break;

				case DASH:
					dummyVektor = opp.getP_tackling().get(move).getPosRel(
							this.nextSelf, RELATIVE.TO_BODY);
					dummyVektor2.pointAtPolar(0.9, Vektor.normalize(opp
							.getPlayer().getBodyDir()
							+ Math.signum(dummyVektor.x) * 90.0));
					dummyVektor2.addToThis(nextSelf.getPosition());
					testPoints.add(dummyVektor2.cloned());
					if (DEBUG)
						System.out.print("Added save point " + dummyVektor);
					break;
				}
			}
		}

		// checking the new points
		LinkedList<Vektor> goodPoints = this
				.checkPositionsAgainstTackling(testPoints);

		if (goodPoints.size() == 0) {
			if (this.mostSecurePoint.isEmpty()) {
				this.dummyVektor.pointAtPolar(0.4, this.nextSelf.getBodyDir());
				this.dummyVektor.addToThis(nextSelf.getPosition());
				if (DEBUG)
					System.out
							.println("No secure point found -> kicking in front of player"
									+ dummyVektor);
			} else {
				this.dummyVektor.copy(this.mostSecurePoint);
				if (DEBUG)
					System.out.println("Most secure point found -> kicking to"
							+ this.mostSecurePoint);

			}
			this.setBenefit(TYPE.SAVING_UNSECURE, 1);
			this.subStateString = DEBUG_SAVINGSECURE;
		} else {
			this.dummyVektor.copy(goodPoints.get(0));
			this.setBenefit(TYPE.SAVING_SECURE, 1);
			this.subStateString = DEBUG_SAVINGUNSECURE;

			if (DEBUG)
				System.out.println("Secure point found -> kicking to"
						+ this.dummyVektor);

		}

		this.bestAction = BasicActionFactory.kickInTime(this.getWorld()
				.getSelfRef(), this.getWorld().getBallRef(), this.dummyVektor,
				1, 0.2);

		// we can not kick to the save point
		if (this.bestAction == null) {
			// if in danger kick the ball away, otherwise stop it
			if (this.nextSelf.getDistance(SConf.getInstance().GOAL_POS_OWN) > 20) {
				this.bestAction = this.stopBall();
			} else {
				this.bestAction = BasicActionFactory.kickFastest(this
						.getWorld().getSelfRef(), this.getWorld().getBallRef(),
						SConf.getInstance().GOAL_POS_OTHER);
			}
		}

		if (DEBUG)
			System.out.println("KICK: " + this.bestAction);

	}

	/**
	 * checks a given number of points against tackling possiblities. this
	 * methods assumes, that the opponents try to tackle the next ball, and is
	 * therefore based on the tackler list
	 * 
	 * @param testPoints -
	 *            the points to check
	 */
	private LinkedList<Vektor> checkPositionsAgainstTackling(
			LinkedList<Vektor> testPoints) {

		LinkedList<Vektor> goodPoints = new LinkedList<Vektor>();
		for (Vektor v : testPoints) {
			goodPoints.add(v);
		}

		double lowestTackleProb = 0.8;
		double tackleProb;

		for (TacklingPlayer opp : this.oppsTackling) {
			for (Player p : opp.p_tackling) {
				for (Vektor point : testPoints) {
					tackleProb = p.tackleSuccessProbability(point);

					if (tackleProb < lowestTackleProb) {
						lowestTackleProb = tackleProb;
						mostSecurePoint = point;
					}

					if (tackleProb > 0.2) {
						if (DEBUG)
							System.out
									.println(point + " can be tackled by" + p);
						goodPoints.remove(point);
					}
				}
			}
		}

		return goodPoints;
	}

	/**
	 * dribbles fast
	 * 
	 */
	private void doFastDribbling() {

		if (DEBUG)
			System.out.println("FAST DRIBBLING");

		if (this.setBenefit(TYPE.FAST_DRIBBLING, this
				.getSecurity(TYPE.FAST_DRIBBLING)))
			this.bestAction = this.getDribbleAction(TYPE.FAST_DRIBBLING);
	}

	/**
	 * dribbles slow
	 * 
	 */
	private void doSlowDribbling() {

		if (DEBUG)
			System.out.println(this.getWorld().getCycleAndPlayerString()
					+ "SLOW DRIBBLING");

		if (this.setBenefit(TYPE.SLOW_DRIBBLING, this
				.getSecurity(TYPE.SLOW_DRIBBLING)))
			this.bestAction = this.getDribbleAction(TYPE.SLOW_DRIBBLING);
	}

	/**
	 * the side to put the ball on when dribbling (depends on the players role
	 * and on the side of the nearest opponents).
	 * 
	 * @return -1 = left, 1 = right (to dribble dir)
	 */
	private int getDribbleSide() {

    //opponents further away than hat are not considered
		double tolerance = 3;

		// on left side put ball more often right for better passing
		if (this.nextBall.getPosition().x < -10) {
			if (this.oppsRight.size() == 0) {
				return 1;
			}
			if (this.oppsLeft.size() == 0) {
				return this.nextSelf.getDistance(this.oppsRight.get(0)) > tolerance ? 1
						: -1;
			}

			double distOppRight = this.nextSelf.getDistance(this.oppsRight
					.get(0));
			double distOppLeft = this.nextSelf
					.getDistance(this.oppsLeft.get(0));

			return distOppRight > distOppLeft - tolerance ? 1 : -1;
		}

		// on right side put ball more often left for better passing
		else if (this.nextBall.getPosition().x > 10) {
			if (this.oppsLeft.size() == 0) {
				return -1;
			}
			if (this.oppsRight.size() == 0) {
				return this.nextSelf.getDistance(this.oppsLeft.get(0)) > tolerance ? -1
						: 1;
			}

			double distOppRight = this.nextSelf.getDistance(this.oppsRight
					.get(0));
			double distOppLeft = this.nextSelf
					.getDistance(this.oppsLeft.get(0));

			return distOppLeft > distOppRight - tolerance ? -1 : 1;
		}
    
		// in center put ball were it is more secure
		else {
			double distOppRight = this.oppsRight.size() == 0 ? 10 : Math.min(
					this.nextSelf.getDistance(this.oppsRight.get(0)), 7);
			double distOppLeft = this.oppsLeft.size() == 0 ? 10 : Math.min(
					this.nextSelf.getDistance(this.oppsLeft.get(0)), 7);

			double ballIsRight = this.getWorld().getSelfRef().getPosRel(
					this.getWorld().getBallRef(), RELATIVE.TO_BODY).x;

			if (ballIsRight > 0.3)
				ballIsRight = 1;
			else if (ballIsRight < -0.3)
				ballIsRight = -1;
			else
				ballIsRight = 0;

			return distOppLeft > distOppRight + ballIsRight * 2 ? -1 : 1;
		}

	}

	/**
	 * dribbles in body dir. the speed is limited by a given dashAction.
	 * 
	 * @param type - the dribble type
	 * @return the best dribble action
	 */
	private Action getDribbleAction(TYPE type) {
		DashAction dashAction = type == TYPE.FAST_DRIBBLING ? this.dash100
				: this.dash50;

		DashAction maxDash = BasicActionFactory.dashToPoint(this.getWorld()
				.getSelfRef(), this.nextBall.getPosition());

    //the dashaction corrected to the Stamina level
		DashAction correctedDash = this.dummyDashAction;
		correctedDash.setPower(dashAction.getPower());

    //save more stamina when dribbling slow
		if (type == TYPE.SLOW_DRIBBLING)
			this.nextSelf.correctDashPower(correctedDash, STAMINA_LEVEL.MEDIUM);
		else
			this.nextSelf.correctDashPower(correctedDash, STAMINA_LEVEL.LOW);

		maxDash.setPower(Math.min(maxDash.getPower(), correctedDash.getPower()));

    //the player after dashing 1x
		Player p_dashing = this.getWorld().getSelfRef()
				.predictPlayerAfterAction(maxDash);

    //next ball will be kcikable for p_dashing
		boolean nextBallIsKickable = p_dashing.getDistance(this.nextBall) < 0.2 + this
				.getWorld().getSelfRef().getPConf().KICKABLE_MARGIN;

		// if kickable try dashing to follow the ball
		if (maxDash.getPower() > 0 && nextBallIsKickable
				&& !this.opponentCanTackleIn1) {
			if (DEBUG)
				System.out.print("Try dashing ");
			Line playerDashLine = new Line(nextSelf.getPosition(), nextSelf
					.getPosition().add(
							new Vektor(Vektor.POLAR, 1, nextSelf.getBodyDir())));

			Circle minBallCircle = new Circle(nextBall.getPosition(), 0.5);

			Circle maxBallCircle = new Circle(nextBall.getPosition(), nextSelf
					.getPConf().KICKABLE_MARGIN + 0.2);

			// try to get as close as possible (best when ball ahead)
			LinkedList<Vektor> intersectionPoints = playerDashLine
					.getIntersectionPoints(minBallCircle);

			// try to get as far as possible (best when ball behind)
			intersectionPoints.addAll(playerDashLine
					.getIntersectionPoints(maxBallCircle));

			DashAction simDash = null;
			DashAction bestDash = null;

			// get the dash action with the max power
			for (Vektor ip : intersectionPoints) {
				simDash = BasicActionFactory.dashToPoint(getWorld()
						.getSelfRef(), ip);

				switch (type) {
				case FAST_DRIBBLING:
					if (simDash.getPower() > correctedDash.getPower() - 20) {
						if (bestDash == null
								|| bestDash.getPower() < simDash.getPower())
							bestDash = simDash;
					}
					continue;

				case SLOW_DRIBBLING:
					if (simDash.getPower() > correctedDash.getPower() - 20) {
						if (bestDash == null
								|| bestDash.getPower() < simDash.getPower())
							bestDash = simDash;
					}
					continue;
				}
			}

			if (bestDash != null) {
				if (DEBUG)
					System.out.println("Dashing " + bestDash);
				return bestDash;
			}
		}

		// no dashing -> try kicking
		int dribbleSide = this.getDribbleSide();
		Action a = null;
		Vektor bestKickPosAbs = null;
		Vektor bestKickPosRel = null;

		switch (type) {
    
		// TODO should there be any differences here?? (rw)
		case SLOW_DRIBBLING:
		case FAST_DRIBBLING:

			// best distance to player
			double r_1 = 0.75;

			// best x offset to player center
			double l_best = 0.6;

			// best y offset to player center
			double halfBallWay = Math.sqrt(r_1 * r_1 - l_best * l_best);

			// best rel. kick pos
			bestKickPosRel = new Vektor(Vektor.POLAR, l_best,
					dribbleSide * 90.0).add(new Vektor(Vektor.POLAR,
					halfBallWay * 0.9, -180.0));

			int i = 1;
			KickAction simKick;
			Ball simBall = Ball.getDummyInstance();
			Player simPlayer = Player.getDummyInstance();
			DashAction simDash = new DashAction(0);

			Player p_ = nextSelf.cloned();

			boolean goodTry = false;

			// this loop simulates ball and player after a kick
			// 1. we try to get the ball to the bestAbsPosition in
			// 'i' cycles
			// 2. we check if the resulting ball will always be in a
			// certain range
			// of the player
			// assuming the player dashes for the ball
			// 3. if(2.) -> remember the kickAction
			// 4. try to kick to a position that is farer away so we
			// can dash more
			// -> i++ -> 1
			while (i < 10) {
				bestKickPosAbs = p_.getPosAbs(bestKickPosRel,
						Player.RELATIVE.TO_BODY);
				simKick = BasicActionFactory.kickInTime(this.getWorld()
						.getSelfRef(), this.getWorld().getBallRef(),
						bestKickPosAbs, i, 0.1);
				simBall.copy(getWorld().getBallRef());
				simBall.predictThisAfterAction(this.getWorld().getSelfRef(),
						simKick);
				int j = 1;
				simPlayer.copy(nextSelf);// =
				// world.getSelfRef().predictPlayerAfterAction(new
				// KickAction(world.isViBased(),0,0.0));
				goodTry = true;
				while (j <= i) {
					if (simPlayer.getDistance(simBall) > p_.getPConf().KICKABLE_MARGIN + 0.05
							|| simPlayer.getDistance(simBall) < 0.5) {
						goodTry = false;
						break;
					}
					simDash.setPower(100);
					simPlayer.predictThisAfterAction(correctedDash);
					simBall.extrapolate(1, SConf.getInstance().BALL_DECAY);
					j++;
				}
				if (goodTry) {
					a = simKick;
					if (DEBUG)
						System.out.println("Could kick to " + bestKickPosAbs
								+ bestAction + correctedDash);
				}
				i++;
				p_.predictThisAfterAction(correctedDash);
			}
		}

		// ball position is bad kicking in dribble direction 
    //  -> put it to a good position
		if (a == null) {
			dummyVektor.pointAt(0.5 * dribbleSide, -0.3);
			dummyVektor.copy(this.nextSelf.getPosAbs(dummyVektor,
					RELATIVE.TO_BODY));
			a = BasicActionFactory.kickInTime(this.getWorld().getSelfRef(),
					this.getWorld().getBallRef(), dummyVektor, 1, 0.1);

			if (DEBUG) {
				if (a == null)
					System.out.println(" No kicking to bestPos " + dummyVektor);
				else
					System.out.println(" Kicking to bestPos " + dummyVektor);
			}
		}

    //we could not even bring the ball to a position where would could kick
    //it in dribble dir the next cycle. As the ball is probably very fast we try
    //to stop it
		if (a == null) {
      
      //try to stop the ball
			a = this.stopBall();

			if (DEBUG)
				System.out.println(" Stopping ball." + a + bestKickPosAbs
						+ bestKickPosRel + dribbleSide);
		} else {
			if (DEBUG)
				System.out.println(" kicking ball." + a + bestKickPosAbs
						+ bestKickPosRel + dribbleSide);
		}

		return a;
	}

	/**
	 * outplays the goalie 
   * 
   * TODO all
	 */
	private void outplayGoalie() {

		if (DEBUG)
			System.out.println("OUTPLAYING GOALIE");
		if (this.gotWrongBodyDir())
			this.doTurning();
		else
			this.doFastDribbling();
	}

	/**
	 * waits with the ball and lets it circulated around the player which might 
   * create better pass situations as the initiale ball speed and positions 
   * vary. If a wing player the side to the field is prefered as its more
   * probable to find someone to pass to in the field's center.
	 */
	private void waitWithBall() {

		if (!this.setBenefit(TYPE.WAIT_WITH_BALL, this
				.getSecurity(TYPE.WAIT_WITH_BALL))) {
			return;
		}

		boolean nextBallIsKickableAndSlow = nextBall.getSpeed().getLength() < 0.3
				&& nextSelf.getDistance(nextBall) < 0.7;
		boolean shouldTurn = this.gotWrongBodyDir();

		// next ball can be handled -> turn each second cycle
		if (nextBallIsKickableAndSlow && shouldTurn) {
			dummyVektor.pointAtPolar(10, this.getNextTurnDir());
			dummyVektor.addToThis(nextSelf.getPosition());
			this.bestAction = BasicActionFactory.turnBodyToPoint(getWorld()
					.getSelfRef(), dummyVektor);
			if (DEBUG)
				System.out.println(this.getWorld().getCycleAndPlayerString()
						+ " Dribbling - Wait: Turning" + this.bestAction);
		}
		// circulate ball otherwise
		else {
			if (shouldTurn)
				this.bestAction = this.stopBall();
			else
				this.bestAction = circulateBall();
		}
	}

	/**
	 * @return true if our body dir differs (much) form the dir to go to
	 */
	private boolean gotWrongBodyDir() {

		if (this.getWorld().getSelfRef().getPosition().y > SConf.getInstance().half_length - 15) {
			return Math.abs(this.nextSelf.getAngleRel(this.pointToGo,
					RELATIVE.TO_BODY)) > 30;
		} else {
			return Math.abs(this.nextSelf.getAngleRel(this.pointToGo,
					RELATIVE.TO_BODY)) > 20;
		}
	}

	/**
	 * @return the next dir to turn to when waiting with the ball
	 */
	private double getNextTurnDir() {

		double dirToGoal = this.nextSelf
				.getAngleTo(SConf.getInstance().GOAL_POS_OTHER);

		double[] dirs;

		if (ROLE.META_POSITION_LEFT_WING.contains(this.nextSelf
				.getRoleByRoleNumber())) {
			dirs = new double[] { 0, 90, dirToGoal };
		} else if (ROLE.META_POSITION_RIGHT_WING.contains(this.nextSelf
				.getRoleByRoleNumber())) {
			dirs = new double[] { 0, -90, dirToGoal };
		} else {
			dirs = new double[] { -90, 0, 90, dirToGoal };
		}

		int idx = (int) (Math.random() * dirs.length);
		double dir = dirs[idx];
		if (Math.abs(nextSelf.getAngleRel(dir, RELATIVE.TO_BODY)) < 20)
			dir = dirs[(idx + 1) % dirs.length];

		return dir;

	}

	/**
	 * circulates the ball around the player.
   * 
   * @see #waitWithBall()
	 */
	private Action circulateBall() {

    //we collided. As the ball during circulation should be slow we
    //prefer not kicking it instead of doing a wrong kick due to the underlying
    //noisy information
		if (this.getWorld().collided() && !this.getWorld().isViBased()) {
			System.out
					.println("Circulate Ball collided in BS-Mode -> no acting");
			return new KickAction(0, 0);
		}
		Action a;

		double angleToBall = this.getWorld().getSelfRef().getAngleRel(
				this.getWorld().getBallRef(), RELATIVE.TO_BODY);
		Vektor bestPos = nextSelf.getPosition().cloned();

		if (Math.abs(angleToBall) > 45 && Math.abs(angleToBall) < 135) {
			this.dummyVektor.pointAtPolar(0.7, nextSelf.getBodyDir());
		} else {
			int dribbleSide = this.getDribbleSide();

			double limit = dribbleSide == 1 ? 0.25 : 0.75;

			if (Math.random() > limit) {
				this.dummyVektor.pointAtPolar(0.7, nextSelf.getBodyDir() + 45);
			} else {
				this.dummyVektor.pointAtPolar(0.7, nextSelf.getBodyDir() - 45);
			}
		}

		bestPos.addToThis(dummyVektor);

		a = BasicActionFactory.kickInTime(this.getWorld().getSelfRef(), this
				.getWorld().getBallRef(), bestPos, 1, 0.2);

    //the kick to the bestPos isn't posiible -> stop the ball
		if (a == null || !this.getWorld().inField(bestPos)) {
			a = this.stopBall();
			if (DEBUG)
				System.out.println(this.getWorld().getCycleAndPlayerString()
						+ "Dribbling - Alterating Ball STOPPING" + a + bestPos);
		} else {
			if (DEBUG)
				System.out.println(this.getWorld().getCycleAndPlayerString()
						+ "Dribbling - Alterating Ball" + a + bestPos);
		}

		return a;
	}

	/**
	 * stops the ball (or at least slows it down as best as possible)
	 * 
	 * @return an action that stops the ball, if possible the ball is stopped in
	 *         front of the player
	 *
   * TODO
   *
   * - move to action factories
   */
	private Action stopBall() {

		Ball b = this.getWorld().getBallRef();
		Player self = this.getWorld().getSelfRef();

		if (DEBUG)
			System.out.print(this.getWorld().getCycleAndPlayerString()
					+ " Trying to stop ball:");

		// try to stop ball
		KickAction kickAction = null;

		// trying to collide as we are heading towards the wrong direction
		if (this.gotWrongBodyDir() && nextSelf.getSpeed().getLength() > 0.1) {
			this.dummyVektor.pointAtPolar(0.1, nextSelf.getBodyDir());
			this.dummyVektor.addToThis(this.nextSelf.getPosition());

			kickAction = BasicActionFactory.kickInTime(self, b,
					this.dummyVektor, 1, 0.3);
			if (DEBUG && kickAction != null)
				System.out.print(" kicking inside player " + kickAction
						+ " Speed:" + self.getSpeed().getLength());

			if (kickAction != null)
				return kickAction;
		}

		// get the point in front of the player in 3 cycles
		// 1 cycle kicking + 2 cycles turning if needed
		// (the extra cycle slows down the ball)
		this.dummyVektor.copy(self.getSpeed());
		this.dummyVektor.mult(RCMath.calculateRow(
				nextSelf.getPConf().PLAYER_DECAY, 3));
		this.dummyVektor.addToThis(self.getPosition());
		this.dummyVektor.addToThis(new Vektor(0.6, this.nextSelf
				.getAngleTo(this.pointToGo)));

		kickAction = BasicActionFactory.kickInTime(self, b, this.dummyVektor,
				3, 0.3);

		if (DEBUG && kickAction != null)
			System.out.print(" kicking to bestPos " + kickAction);

		if (kickAction == null) {
			double angleToBall = this.nextSelf.getAngleRel(this.nextBall,
					RELATIVE.TO_BODY);

			// best we can do ;-)
			kickAction = new KickAction(100, Vektor
					.normalize(180 + angleToBall));
			if (DEBUG)
				System.out.print(" slowing down ball " + kickAction);
		}

		return kickAction;
	}

	/**
	 * the number of cycles an opponent needs to get the ball. (which is pretty
	 * complicated to tell as we can move the ball around ;-))
	 * 
	 * @param type -
	 *            the dribble type
	 * @return the number of cycles an opponent needs to intercept us
	 */
	private int getSecurity(TYPE type) {

		int security = maxSecurity;

		switch (type) {
		case WAIT_WITH_BALL:
			if (this.oppsRelevant.size() > 0) {
				// distance of nearest opp
				security = (int) (this.getWorld().getSelfRef().getDistance(
						this.oppsRelevant.get(0)) - this.oppsRelevant.get(0)
						.getKickDistance());

				// -age of nearest opp (max = 2, as we feel near opponents)
				security -= Math.min(this.oppsRelevant.get(0).getAge(
						this.getWorld().getBodyCycle(),
						UPDATE_MODE.META_BASED_ON_INFO), 2);
			}
			break;

		case FAST_DRIBBLING:
		case SLOW_DRIBBLING:
			if (this.oppsInBodyDir.size() > 0) {
				// distance of nearest opp
				security = (int) (this.getWorld().getSelfRef().getDistance(
						this.oppsInBodyDir.get(0)) - this.oppsInBodyDir.get(0)
						.getKickDistance());

				// -age of nearest opp (max = 2, as we feel near opponents)
				security -= Math.min(this.oppsInBodyDir.get(0).getAge(
						this.getWorld().getBodyCycle(),
						UPDATE_MODE.META_BASED_ON_INFO), 2);
			} else if (this.oppsRelevant.size() > 0) {
				// distance of nearest opp
				security = (int) (1.4 * (this.getWorld().getSelfRef()
						.getDistance(this.oppsRelevant.get(0)) - this.oppsRelevant
						.get(0).getKickDistance()));

				// -age of nearest opp (max = 2, as we feel near opponents)
				security -= Math.min(this.oppsRelevant.get(0).getAge(
						this.getWorld().getBodyCycle(),
						UPDATE_MODE.META_BASED_ON_INFO), 2);
			}
			break;

		case SAVING_SECURE:
			return 1;
		case SAVING_UNSECURE:
			return 0;

		case BACKWARD_DRIBBLING:
		case TURNING:
			return this.oppsNear.size() == 0 ? DribbleSituation.maxSecurity
					: (int) this.oppsNear.get(0).getDistance(
							this.nextSelf.getPosition()) - 1;
		}

		if (this.opponentCanTackleIn1)
			security--;

		if (security < 0)
			security = 0;

		if (security > maxSecurity)
			security = maxSecurity;

		return security;
	}

	/**
	 * sets the benefit for the situation.
   * The benefit depends on the chosen dribble behaviour and the security.
	 * 
	 * @param type -
	 *            dibble type
	 * @param security -
	 *            the opponents interception time
	 * @return true if dribble benefit > pass benefit
	 */
	private boolean setBenefit(TYPE type, int security) {

    //the SituationEvaluator does not make a difference between
    // the different dribble behaviours. This is done here.
		switch (type) {
		case TURNING:
			this.benefit = SituationEvaluator.getValue(
					SituationEvaluator.TYPE.WAIT_WITH_BALL, this.getWorld()
							.getSelfRef().getPosition(), this.getWorld()
							.getSelfRef().getPosition(), Math.max(security - 1,
							0), 1, this.getWorld().getSelfRef(), this
							.getWorld());
			break;

		case WAIT_WITH_BALL:
		case SAVING_SECURE:
			this.benefit = SituationEvaluator.getValue(
					SituationEvaluator.TYPE.WAIT_WITH_BALL, this.getWorld()
							.getSelfRef().getPosition(), this.getWorld()
							.getSelfRef().getPosition(), Math.max(security - 1,
							0), 1, this.getWorld().getSelfRef(), this
							.getWorld()) * 0.3;
			break;

		case SAVING_UNSECURE:
			this.benefit = SituationEvaluator.getValue(
					SituationEvaluator.TYPE.WAIT_WITH_BALL, this.getWorld()
							.getSelfRef().getPosition(), this.getWorld()
							.getSelfRef().getPosition(), Math.max(security - 1,
							0), 1, this.getWorld().getSelfRef(), this
							.getWorld()) * 0.1;
			break;

		case SLOW_DRIBBLING:
			dummyVektor.pointAtPolar(3, this.nextSelf.getBodyDir());
			dummyVektor.addToThis(this.nextSelf.getPosition());

			this.benefit = SituationEvaluator.getValue(
					SituationEvaluator.TYPE.WAIT_WITH_BALL, this.getWorld()
							.getSelfRef().getPosition(), dummyVektor, Math.max(
							security - 1, 0), 1, this.getWorld().getSelfRef(),
					this.getWorld()) * 0.9;
			break;

		case FAST_DRIBBLING:
			dummyVektor.pointAtPolar(7, this.nextSelf.getBodyDir());
			dummyVektor.addToThis(this.nextSelf.getPosition());

			this.benefit = SituationEvaluator.getValue(
					SituationEvaluator.TYPE.WAIT_WITH_BALL, this.getWorld()
							.getSelfRef().getPosition(), dummyVektor, Math.max(
							security, 0), 1, this.getWorld().getSelfRef(), this
							.getWorld());
			break;

		}
		if (DEBUG)
			System.out.println(type + " Benefit: " + this.benefit + " Best:"
					+ this.passBenefit + " Sec:" + security);

		this.lastType = type;

		return this.benefit > this.passBenefit;
	}

	/**
	 * prints the opponent lists
	 */
	@SuppressWarnings("unused")
	private void printOpponentLists() {

		System.out.println(this.getWorld().getCycleAndPlayerString()
				+ " DribbleSituation. Consider opponents:"
				+ this.getWorld().getSelfRef());

		System.out.print("Near: ");
		for (Player o : this.oppsNear) {
			System.out.print(o.getNumber() + " ");
		}
		System.out.println();

		System.out.print("In Body Dir: ");
		for (Player o : this.oppsInBodyDir) {
			System.out.print(o.getNumber() + " ");
		}
		System.out.println();

		System.out.print("In goal dir: ");
		for (Player o : this.oppsInGoalDir) {
			System.out.print(o.getNumber() + " ");
		}
		System.out.println();

		System.out.print("In back dir: ");
		for (Player o : this.oppsInBackDir) {
			System.out.print(o.getNumber() + " ");
		}
		System.out.println();
	}

}
